package fleporcq.simpleReflect;

import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

public class FieldDescriptor {

    protected Field field;

    protected static final String PARAMS_CONTAINER_JOKER = "*";

    protected static final String PARAMS_CONTAINER = "<" + PARAMS_CONTAINER_JOKER + ">";

    protected static final String PARAMS_SEPARATOR = ", ";

    protected static final String PACKAGE_SEPARATOR = ".";

    public FieldDescriptor(Field field) {
        this.field = field;
    }

    public String getName() {
        return this.field.getName();
    }

    private String getType() {
        Type genericType = this.field.getGenericType();

        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            StringBuilder typeBuilder = new StringBuilder();
            String rawType = parameterizedType.getRawType().toString();
            typeBuilder.append(extractSimpleName(rawType));
            Type[] args = parameterizedType.getActualTypeArguments();
            typeBuilder.append(StringUtils.replace(PARAMS_CONTAINER, PARAMS_CONTAINER_JOKER, getTypeParams(args)));
            return typeBuilder.toString();

        }

        return this.field.getType().getSimpleName();

    }


    public EVisibility getVisibility() {
        if (this.isPublic()) {
            return EVisibility.PUBLIC;
        } else if (this.isPrivate()) {
            return EVisibility.PRIVATE;
        } else if (this.isProtected()) {
            return EVisibility.PROTECTED;
        }
        return null;
    }

    public boolean isPublic() {
        int modifier = this.field.getModifiers();
        return Modifier.isPublic(modifier);
    }

    public boolean isPrivate() {
        int modifier = this.field.getModifiers();
        return Modifier.isPrivate(modifier);
    }

    public boolean isProtected() {
        int modifier = this.field.getModifiers();
        return Modifier.isProtected(modifier);
    }

    public boolean isStatic() {
        int modifier = this.field.getModifiers();
        return Modifier.isStatic(modifier);
    }

    private static String extractSimpleName(String name) {
        return name.substring(name.lastIndexOf(PACKAGE_SEPARATOR) + 1);
    }

    private static String getTypeParams(Type[] args) {
        StringBuilder argsBuilder = new StringBuilder();
        for (Type arg : args) {
            String argType = arg.toString();
            argsBuilder.append(getTypeClass(arg).getSimpleName());
            argsBuilder.append(PARAMS_SEPARATOR);
        }
        int length = argsBuilder.length();
        if (length > 0) {
            argsBuilder.delete(length - PARAMS_SEPARATOR.length(), length);
        }
        return argsBuilder.toString();
    }

    //Todo : déporter cette fonction dans la futur classe permettant de générer le graph UML
    public ClassDescriptor getUmlUseRelation() {
        Class fieldType = this.field.getType();
        Class usedClass = null;
        if (Collection.class.isAssignableFrom(fieldType)) {
            ParameterizedType parameterizedType = (ParameterizedType)this.field.getGenericType();
            Type[] args = parameterizedType.getActualTypeArguments();
            usedClass = this.getTypeClass(args[0]);
        }else if(fieldType.isArray()){

        }else if(fieldType.isPrimitive()){

        }else if(Package.getPackage("java.lang") != fieldType.getPackage()){
            usedClass = fieldType;
        }

        if(usedClass != null){
            return new ClassDescriptor(usedClass);
        }

        return null;
    }



    private static Class getTypeClass(Type type)  {
        Class clazz = null;
        try {
            clazz = ((Class) type).newInstance().getClass();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return clazz;
    }

    @Override
    public String toString() {
        return this.getVisibility() + " " + this.getName() + " : " + this.getType();
    }
}
